package cn.xzc.job.cocurrent;

import cn.xzc.job.ErrorHandler;
import cn.xzc.job.JobScheduler;
import cn.xzc.job.Trigger;
import cn.xzc.job.constant.Constant;
import cn.xzc.job.type.SimpleTimerContext;
import cn.xzc.job.util.JobUtils;
import jakarta.enterprise.concurrent.LastExecution;
import jakarta.enterprise.concurrent.ManagedScheduledExecutorService;

import java.time.Clock;
import java.util.Date;
import java.util.concurrent.*;

/**
 * job一种定时执行线程池
 *
 * @author xzc
 * 当前时间 2024-02-12 21:30:00
 */
public class ConcurrentJobScheduler extends ConcurrentJobExecutor implements JobScheduler {

    private static Class<?> managedScheduledExecutorServiceClass;

    static {
        try {
            managedScheduledExecutorServiceClass = Class.forName(
                    Constant.MANAGED_SCHEDULED_EXECUTOR_SERVICE_CLASS, false,
                    ConcurrentJobScheduler.class.getClassLoader());
        } catch (ClassNotFoundException ex) {
            // JSR-236 API not available...
            managedScheduledExecutorServiceClass = null;
        }
    }

    private ScheduledExecutorService scheduledExecutor;

    private boolean enterpriseConcurrentScheduler = false;

    private ErrorHandler errorHandler;

    private Clock clock = Clock.systemDefaultZone();


    /**
     * Create a new ConcurrentJobScheduler,
     * using a single thread executor as default.
     *
     * @see java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
     */
    public ConcurrentJobScheduler() {
        super();
        this.scheduledExecutor = initScheduledExecutor(null);
    }

    /**
     * Create a new ConcurrentJobScheduler, using the given
     * {@link java.util.concurrent.ScheduledExecutorService} as shared delegate.
     * <p>Autodetects a JSR-236 {@link jakarta.enterprise.concurrent.ManagedScheduledExecutorService}
     * in order to use it for trigger-based scheduling if possible,
     * instead of Spring's local trigger management.
     *
     * @param scheduledExecutor the {@link java.util.concurrent.ScheduledExecutorService}
     */
    public ConcurrentJobScheduler(ScheduledExecutorService scheduledExecutor) {
        super(scheduledExecutor);
        this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
    }

    /**
     * Create a new ConcurrentJobScheduler, using the given {@link java.util.concurrent.Executor}
     * and {@link java.util.concurrent.ScheduledExecutorService} as delegates.
     * <p>Autodetects a JSR-236 {@link jakarta.enterprise.concurrent.ManagedScheduledExecutorService}
     * in order to use it for trigger-based scheduling if possible,
     * instead of Spring's local trigger management.
     *
     * @param concurrentExecutor the {@link java.util.concurrent.Executor} to delegate to
     */
    public ConcurrentJobScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
        super(concurrentExecutor);
        this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
    }


    private ScheduledExecutorService initScheduledExecutor(
                                                           ScheduledExecutorService scheduledExecutor) {
        if (scheduledExecutor != null) {
            this.scheduledExecutor = scheduledExecutor;
            this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
                    managedScheduledExecutorServiceClass.isInstance(scheduledExecutor));
        } else {
            this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
            this.enterpriseConcurrentScheduler = false;
        }
        return this.scheduledExecutor;
    }

    /**
     * Specify the {@link java.util.concurrent.ScheduledExecutorService} to delegate to.
     * <p>Autodetects a JSR-236 {@link jakarta.enterprise.concurrent.ManagedScheduledExecutorService}
     * in order to use it for trigger-based scheduling if possible,
     * instead of Spring's local trigger management.
     */
    public void setScheduledExecutor(
                                     ScheduledExecutorService scheduledExecutor) {
        initScheduledExecutor(scheduledExecutor);
    }

    /**
     * Provide an {@link ErrorHandler} strategy.
     */
    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    @Override
    public Clock getClock() {
        return this.clock;
    }

    /**
     * Set the clock to use for scheduling purposes.
     * <p>The default clock is the system clock for the default time zone.
     *
     * @see Clock#systemDefaultZone()
     */
    public void setClock(Clock clock) {
        this.clock = clock;
    }

    @Override
    
    public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
        try {
            if (this.enterpriseConcurrentScheduler) {
                return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);
            } else {
                ErrorHandler errorHandler =
                        (this.errorHandler != null ? this.errorHandler : JobUtils.getDefaultErrorHandler(true));
                return new ReschedulingRunnable(task, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule();
            }
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
        long initialDelay = startTime.getTime() - this.clock.millis();
        try {
            return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
        long initialDelay = startTime.getTime() - this.clock.millis();
        try {
            return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
        try {
            return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
        long initialDelay = startTime.getTime() - this.clock.millis();
        try {
            return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
        try {
            return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ex) {
            throw new RejectedExecutionException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
        }
    }

    private Runnable decorateTask(Runnable task, boolean isRepeatingTask) {
        Runnable result = JobUtils.decorateTaskWithErrorHandler(task, this.errorHandler, isRepeatingTask);
        if (this.enterpriseConcurrentScheduler) {
            result = ManagedTaskBuilder.buildManagedTask(result, task.toString());
        }
        return result;
    }


    /**
     * Delegate that adapts a Spring Trigger to a JSR-236 Trigger.
     * Separated into an inner class in order to avoid a hard dependency on the JSR-236 API.
     */
    private class EnterpriseConcurrentTriggerScheduler {

        public ScheduledFuture<?> schedule(Runnable task, final Trigger trigger) {
            ManagedScheduledExecutorService executor = (ManagedScheduledExecutorService) scheduledExecutor;
            return executor.schedule(task, new jakarta.enterprise.concurrent.Trigger() {
                @Override
                
                public Date getNextRunTime(
                                           LastExecution le, Date taskScheduledTime) {
                    return (trigger.nextExecutionTime(le != null ?
                            new SimpleTimerContext(le.getScheduledStart(), le.getRunStart(), le.getRunEnd()) :
                            new SimpleTimerContext()));
                }

                @Override
                public boolean skipRun(LastExecution lastExecution, Date scheduledRunTime) {
                    return false;
                }
            });
        }
    }

}
